/*
 * BaseClient.java
 *
 * Created on May 6, 2007, 10:38 AM
 *
 *
 * Copyright (c) 2008 Golden T Studios.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package jinyilw.tools.net.dev;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public abstract class BaseClient
{
	private final NetworkPacket[] nullPacket = new NetworkPacket[0];
	private final NetworkPacket[] receivedPackets1 = new NetworkPacket[1];
	private final NetworkPacket[] receivedPackets2 = new NetworkPacket[2];

	public static int defaultWaitTimeOut = 10000;
	public static int defaultUpdateTime = 100;

	protected BaseServer server;

	private boolean hasClientID = false;
	private short clientID = -1;
	private String groupName = null;
	private Object info;

	private NetworkPacket[] receivedPackets = nullPacket;

	protected long lastPing = System.currentTimeMillis();
	protected long lastReceivedPing;

	private int pingTime = NetworkConfig.getDefaultPingTime();
	private final List<NetworkPacket> listPackets = new ArrayList<>();

	private boolean connected = true;

	public BaseClient()
	{
	}

	protected BaseClient(BaseServer server)
	{
		this.server = server;
	}

	public void update(long elapsedTime) throws IOException
	{
		clearConsumedPacket();

		if (pingTime != -1)
		{
			if (System.currentTimeMillis() - lastPing > pingTime)
			{
				lastPing = System.currentTimeMillis();
				ping();
			}
		}
	}

	public boolean sendCommand(CommandPacket commandPacket)
	{
		if (commandPacket == null || !isConnected())
			return false;
		try
		{
			sendPacket(commandPacket);
			return true;
		} catch (IOException e)
		{
			System.out.println("命令包发送失败：" + commandPacket.commandID);
			e.printStackTrace();
			return false;
		}
	}

	public boolean sendCommand(CommandPacket commandPacket, short commandID)
	{
		if (commandPacket == null)
			return false;
		commandPacket.setCommandID(commandID);
		return sendCommand(commandPacket);
	}

	public void sendPacket(NetworkPacket packet) throws IOException
	{
		if (!isConnected())
			return;
		if (server == null && packet.isSendSender())
		{
			packet.setSender(this);
		}

		if (NetworkConfig.DEBUG && packet != NetworkPing.getInstance())
		{
			System.out.println("SEND packet to "
					+ ((server == null) ? "Server" : getCompleteDetail()));
			System.out.println(packet);
			System.out.println();
		}

		PacketManager packetManager = NetworkConfig.getPacketManager();
		byte[] data = packetManager.pack(packet);
		sendPacket(data);
	}

	public void sendRawPacket(byte[] data) throws IOException
	{
		if (NetworkConfig.getPacketManager() != null)
		{
			throw new RuntimeException("The PacketManager is exists.\n"
					+ "Configure NetworkConfiguration.setPacketManager(null) to send a raw packet"
					+ ".");
		}

		sendPacket(data);
	}

	public void ping() throws IOException
	{
		if (NetworkConfig.getPacketManager() == null)
		{
			sendPacket(NetworkPing.ping);
		} else
		{
			sendPacket(NetworkPing.getInstance());
		}
	}

	protected abstract void sendPacket(byte[] data) throws IOException;

	protected abstract void disconnectImpl() throws IOException;

	public void disconnect() throws IOException
	{
		if (connected)
		{
			connected = false;
			if (server != null)
				server.removeClient(this);
		}
		disconnectImpl();
	}

	public void silentDisconnect()
	{
		try
		{
			disconnect();
		} catch (IOException ignored)
		{
		}
	}

	public abstract boolean isConnected();

	public NetworkPacket waitForPacket(boolean update, long updateTime,
			int waitTimeOut) throws IOException
	{
		long startTime = System.currentTimeMillis();

		while (true)
		{
			if (update)
			{
				update(updateTime);
			}
			if (receivedPackets.length > 0)
			{
				return receivedPackets[0];
			}
			if (waitTimeOut != -1)
			{
				if (System.currentTimeMillis() - startTime > waitTimeOut)
				{
					throw new NetworkException(
							"Packet time out " + waitTimeOut + "ms");
				}
			}
			try
			{
				Thread.sleep(updateTime);
			} catch (InterruptedException ignored)
			{
			}
		}
	}

	public NetworkPacket waitForPacket(boolean update)
			throws IOException
	{
		return waitForPacket(update, BaseClient.defaultUpdateTime,
				BaseClient.defaultWaitTimeOut);
	}

	public NetworkPacket waitForPacket(boolean update, short id,
			long updateTime, int waitTimeOut)
			throws IOException
	{
		long startTime = System.currentTimeMillis();

		while (true)
		{
			if (update)
			{
				update(updateTime);
			}
			NetworkPacket packet = getReceivedPacket(id);
			if (packet != null)
			{
				return packet;
			}
			if (waitTimeOut != -1)
			{
				if (System.currentTimeMillis() - startTime > waitTimeOut)
				{
					throw new NetworkException(
							"Packet time out " + waitTimeOut + "ms");
				}
			}
			try
			{
				Thread.sleep(updateTime);
			} catch (InterruptedException ignored)
			{
			}
		}
	}

	public NetworkPacket waitForPacket(boolean update, short id)
			throws IOException
	{
		return waitForPacket(update, id, BaseClient.defaultUpdateTime,
				BaseClient.defaultWaitTimeOut);
	}

	public NetworkPacket waitForPacketCode(boolean update, short code,
			long updateTime, int waitTimeOut)
			throws IOException
	{
		long startTime = System.currentTimeMillis();

		while (true)
		{
			if (update)
			{
				update(updateTime);
			}

			NetworkPacket packet = getReceivedPacketCode(code);
			if (packet != null)
			{
				packet.consume();
				return packet;
			}

			if (waitTimeOut != -1)
			{
				if (System.currentTimeMillis() - startTime > waitTimeOut)
				{
					throw new NetworkException(
							"Packet time out " + waitTimeOut + "ms");
				}
			}

			try
			{
				Thread.sleep(updateTime);
			} catch (InterruptedException ignored)
			{
			}
		}
	}

	public NetworkPacket waitForPacketCode(boolean update, short code)
			throws IOException
	{
		return waitForPacketCode(update, code, BaseClient.defaultUpdateTime,
				BaseClient.defaultWaitTimeOut);
	}

	public boolean isReceivedPacket()
	{
		return (receivedPackets.length > 0);
	}

	public NetworkPacket getReceivedPacket()
	{
		return (receivedPackets.length > 0) ? receivedPackets[0] : null;
	}

	public NetworkPacket getReceivedPacket(short id)
	{
		for (NetworkPacket receivedPacket : receivedPackets)
		{
			if (receivedPacket.getID() == id)
			{
				return receivedPacket;
			}
		}
		return null;
	}

	public NetworkPacket getReceivedPacketCode(short code)
	{
		for (NetworkPacket receivedPacket : receivedPackets)
		{
			if (receivedPacket.isSendCode()
					&& receivedPacket.getCode() == code)
			{
				return receivedPacket;
			}
		}
		return null;
	}

	public NetworkPacket[] getReceivedPackets()
	{
		return receivedPackets;
	}

	protected void addReceivedPacket(byte[] data) throws IOException
	{
		if (!hasClientID)
		{
			setClientID(Short.parseShort(new String(data)));
			return;
		}

		PacketManager manager = NetworkConfig.getPacketManager();
		NetworkPacket packet;
		if (manager != null)
		{
			packet = manager.unpack(data);
		} else
		{
			if (data.length == 0)
			{
				packet = NetworkPing.getInstance();
			} else
			{
				packet = new NetworkRawPacket(data);
			}
		}

		if (NetworkConfig.DEBUG && packet != NetworkPing.getInstance())
		{
			System.out.println("RECEIVED packet from "
					+ ((server == null) ? "Server" : getCompleteDetail()));
			System.out.println(packet);
			System.out.println();
		}

		lastReceivedPing = System.currentTimeMillis();
		if (packet == NetworkPing.getInstance())
		{
			if (server != null)
			{
				ping();
			}
			return;
		}

		if (receivedPackets == nullPacket)
		{
			receivedPackets = receivedPackets1;
			receivedPackets[0] = packet;

		} else if (receivedPackets == receivedPackets1)
		{
			receivedPackets = receivedPackets2;
			receivedPackets[0] = receivedPackets1[0];
			receivedPackets[1] = packet;

		} else
		{
			receivedPackets = (NetworkPacket[]) NetworkUtil
					.expand(receivedPackets, 1);
			receivedPackets[receivedPackets.length - 1] = packet;
		}

		if (server != null)
		{
			server.addReceivedPacketClient(this);
		}
	}

	protected void clearConsumedPacket()
	{
		if (receivedPackets.length > 0)
		{
			for (NetworkPacket receivedPacket : receivedPackets)
			{
				if (!receivedPacket.isConsumed()
						&& !receivedPacket.isExpired())
				{
					listPackets.add(receivedPacket);
				}
			}

			if (listPackets.isEmpty())
			{
				receivedPackets = nullPacket;
			} else
			{
				if (listPackets.size() == 1)
				{
					receivedPackets = listPackets
							.toArray(receivedPackets1);
				} else if (listPackets.size() == 2)
				{
					receivedPackets = listPackets
							.toArray(receivedPackets2);
				} else
				{
					receivedPackets = listPackets
							.toArray(nullPacket);
				}

				if (NetworkConfig.DEBUG)
				{
					System.out.println(
							"Not consumed packet = " + receivedPackets.length);
					if (server != null)
					{
						System.out.println("On client " + getCompleteDetail());
					}
					for (int i = 0; i < receivedPackets.length; i++)
					{
						System.out.println("Not consumed #" + (i + 1) + " "
								+ receivedPackets[i]);
					}
					System.out.println();
				}
				listPackets.clear();
			}
		}
	}

	public void clearReceivedPacket()
	{
		receivedPackets = nullPacket;
	}

	public int getPingTime()
	{
		return pingTime;
	}

	public void setPingTime(int pingTime)
	{
		this.pingTime = pingTime;
	}

	public short getClientID()
	{
		return clientID;
	}

	public void setClientID(short clientID)
	{
		this.clientID = clientID;
		hasClientID = true;

		if (NetworkConfig.DEBUG)
		{
			System.out.println("========================");
			System.out.println("CONNECTED CLIENT ID: " + clientID);
			System.out.println("========================");
		}
	}

	public Object getInfo()
	{
		return info;
	}

	public void setInfo(Object info)
	{
		this.info = info;
	}

	public boolean isGroupName(String groupName)
	{
		return Objects.equals(this.groupName, groupName);
	}

	public String getGroupName()
	{
		return groupName;
	}

	public void setGroupName(String groupName)
	{
		if (server != null)
		{
			server.changeGroup(this, this.groupName, groupName);
		}

		this.groupName = groupName;
	}

	void clearGroupName()
	{
		groupName = null;
	}

	public abstract String getDetail();

	public abstract String getRemoteDetail();

	public String getCompleteDetail()
	{
		StringBuilder buff = new StringBuilder();
		buff.append("Client ID ");
		if (hasClientID)
		{
			buff.append(clientID);
		} else
		{
			buff.append("[no-id]");
		}
		buff.append(" ");
		buff.append("[");

		if (server != null)
		{
			String groupName = getGroupName();
			if (groupName == null)
			{
				groupName = "[no-group]";
			}
			buff.append("group=").append(groupName).append("; ");
		}
		buff.append(getRemoteDetail());
		if (getInfo() != null)
		{
			buff.append("; info=").append(getInfo());
		}
		buff.append("]");
		return buff.toString();
	}

	public long getLastReceivedPing()
	{
		return lastReceivedPing;
	}

	public File[] getReceivedFiles()
	{
		return null;
	}

	public void getFiles(String... filePaths)
	{

	}

	public File[] getFilesAndWait(int timeout, String... fileNames)
	{
		return null;
	}

	public void sendFiles(File... files)
	{

	}
}
